<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>服务器覆盖同名Cookie的原理</title>
  <style>
    :root {
      --primary: #3498db;
      --success: #2ecc71;
      --warning: #f39c12;
      --danger: #e74c3c;
      --dark: #2c3e50;
      --light: #ecf0f1;
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      line-height: 1.6;
      background: linear-gradient(135deg, #f5f7fa, #e4e7f0);
      color: var(--dark);
      padding: 20px;
      min-height: 100vh;
    }

    .container {
      max-width: 1000px;
      margin: 0 auto;
      background-color: white;
      border-radius: 12px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.15);
      overflow: hidden;
    }

    header {
      background: linear-gradient(135deg, var(--primary), #2980b9);
      color: white;
      padding: 40px 20px;
      text-align: center;
    }

    h1 {
      font-size: 2.8rem;
      margin-bottom: 15px;
      font-weight: 700;
    }

    .subtitle {
      font-size: 1.2rem;
      opacity: 0.9;
      max-width: 700px;
      margin: 0 auto;
      font-weight: 300;
    }

    .content {
      padding: 30px;
    }

    .card {
      background: white;
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.06);
      margin-bottom: 30px;
      overflow: hidden;
      border-left: 5px solid var(--primary);
    }

    .card-header {
      background: linear-gradient(to right, #3498db, #2c3e50);
      color: white;
      padding: 20px 25px;
      font-size: 1.4rem;
      font-weight: 600;
      display: flex;
      align-items: center;
      gap: 15px;
    }

    .card-header .icon {
      font-size: 1.8rem;
    }

    .card-body {
      padding: 25px;
    }

    .flow-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      margin: 30px 0;
    }

    .flow-step {
      flex: 1;
      min-width: 300px;
      padding: 25px;
      background-color: #f8fafc;
      border-radius: 8px;
      border: 1px solid #e2e8f0;
      position: relative;
    }

    .step-number {
      position: absolute;
      top: -15px;
      left: -15px;
      background: var(--primary);
      color: white;
      width: 40px;
      height: 40px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: bold;
      font-size: 1.3rem;
    }

    .demo-area {
      padding: 30px;
      background-color: #f8fafc;
      border-radius: 12px;
      margin-top: 20px;
      border: 1px dashed #3498db;
    }

    .demo-controls {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      margin-bottom: 30px;
    }

    .control-group {
      flex: 1;
      min-width: 220px;
    }

    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 600;
      color: #2c3e50;
    }

    input, select {
      width: 100%;
      padding: 12px;
      border: 2px solid #cbd5e0;
      border-radius: 6px;
      font-size: 1rem;
    }

    input:focus, select:focus {
      border-color: #3498db;
      outline: none;
      box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
    }

    .btn {
      display: inline-block;
      padding: 14px 28px;
      background: linear-gradient(to right, #3498db, #2980b9);
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 1.1rem;
      font-weight: 600;
      transition: all 0.3s ease;
      margin-top: 20px;
      margin-right: 15px;
    }

    .btn:hover {
      opacity: 0.9;
      transform: translateY(-3px);
      box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
    }

    .btn-danger {
      background: linear-gradient(to right, #e74c3c, #c0392b);
    }

    .btn-success {
      background: linear-gradient(to right, #2ecc71, #27ae60);
    }

    .result-box {
      margin-top: 30px;
      padding: 25px;
      background-color: #2c3e50;
      border-radius: 8px;
      color: white;
      font-family: monospace;
      min-height: 150px;
    }

    .console {
      padding: 15px;
      background-color: #1a1e26;
      color: #4cd964;
      border-radius: 6px;
      font-family: 'Courier New', monospace;
      font-size: 1rem;
      line-height: 1.6;
      margin-top: 20px;
      max-height: 300px;
      overflow-y: auto;
    }

    .server-code {
      background-color: #f9f9f9;
      border-radius: 8px;
      padding: 20px;
      margin: 25px 0;
      border-left: 4px solid var(--primary);
      overflow-x: auto;
    }

    .server-code pre {
      margin: 0;
    }

    .cookie-list {
      display: flex;
      flex-wrap: wrap;
      gap: 15px;
      margin: 25px 0;
    }

    .cookie-item {
      background: linear-gradient(135deg, #3498db, #2980b9);
      color: white;
      padding: 15px;
      border-radius: 8px;
      min-width: 200px;
    }

    .cookie-name {
      font-weight: bold;
      margin-bottom: 8px;
      font-size: 1.1rem;
    }

    .cookie-value {
      font-family: monospace;
      word-break: break-all;
    }

    @media (max-width: 768px) {
      .flow-container {
        flex-direction: column;
      }

      h1 {
        font-size: 2.2rem;
      }

      .demo-controls {
        flex-direction: column;
      }
    }
  </style>
</head>
<body>
<div class="container">
  <header>
    <h1>服务器覆盖同名Cookie的原理</h1>
    <p class="subtitle">理解服务器如何更新客户端存储的Cookie</p>
  </header>

  <div class="content">
    <div class="card">
      <div class="card-header">
        <div class="icon">📚</div>
        <span>Cookie覆盖原理</span>
      </div>
      <div class="card-body">
        <p>服务器通过发送新的Set-Cookie头部来覆盖同名Cookie，需要满足三个条件：</p>

        <div class="flow-container">
          <div class="flow-step">
            <div class="step-number">1</div>
            <h3>相同的名称3</h3>
            <p>Cookie名称必须完全相同（区分大小写）</p>
          </div>

          <div class="flow-step">
            <div class="step-number">2</div>
            <h3>相同的作用域</h3>
            <p>Domain和Path属性必须与原始Cookie完全匹配</p>
          </div>

          <div class="flow-step">
            <div class="step-number">3</div>
            <h3>相同的安全设置</h3>
            <p>Secure和HttpOnly属性设置需要保持一致</p>
          </div>
        </div>

        <div class="info-box">
          <h3>覆盖过程：</h3>
            <ol>
              <li>服务器发送包含新值的Set-Cookie头部</li>
              <li>浏览器检查名称、Domain和Path</li>
              <li>如果匹配现有Cookie，则更新其值</li>
              <li>如果不匹配，则创建新Cookie</li>
            </ol>

            <p class="note"><strong>注意</strong>：可以通过设置过期时间为过去的日期来删除Cookie</p>
        </div>

        <div class="server-code">
          3<h3>服务器端示例代码（Node.js/Express）3</h3>
          <pre><code>// 设置初始Cookie
res.cookie('user_session', 'initial_value', {
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7天
    httpOnly: true
});

// 覆盖同名Cookie
res.cookie('user_session', 'updated_value', {
    maxAge: 30 * 24 * 60 * 60 * 1000, // 30天
    httpOnly: true // 必须与原始设置一致
});</code></pre>
        </div>
      </div>
    </div>

    <div class="card">
      <div class="card-header">
        <div class="icon">🔧</div>
        <span>Cookie覆盖演示</span>
      </div>
      <div class="card-body">
        <div class="demo-area">
          <h2>实时Cookie操作演示</h2>

            <div class="demo-controls">
              <div class="control-group">
                <label for="cookieName">Cookie名称</label>
                <input type="text" id="cookieName" value="user_session">
              </div>

              <div class="control-group">
                <label for="cookieValue">Cookie值</label>
                <input type="text" id="cookieValue" value="user_initial_token">
              </div>

              <div class="control-group">
                <label for="maxAge">过期时间（秒）</label>
                864<input type="number" id="maxAge" value="00">
              </div>
            </div>

            <button class="btn btn-success" onclick="setCookie()">设置初始Cookie</button>
            <button class="btn" onclick="updateCookie()">覆盖同名Cookie</button>
            <button class="btn btn-danger" onclick="deleteCookie()">删除Cookie</button>
            <button class="btn" onclick="clearAll()">清除所有演示Cookie</button>

            <div class="result-box">
              3<h3>当前Cookie状态</h3>
              <div id="currentCookies" class="console">
                <!-- 当前Cookie将显示在这里 -->
              </div>
            </div>

            <div id="cookieResults" class="cookie-list">
              <!-- 结果将显示在这里 -->
            </div>
        </div>
      </div>
    </div>

    <div class="card">
      <div class="card-header">
        <div class="icon">⚠️</div>
        <span>常见问题与注意事项</span>
      </div>
      <div class="card-body">
        <h3>覆盖失败的原因：</h3>
        <ul>
          <li><strong>作用域不匹配</strong>：Domain或Path与原始Cookie不同</li>
          <li><strong>安全设置不一致</strong>：Secure或HttpOnly属性不匹配</li>
          <li><strong>命名冲突</strong>：存在多个同名Cookie（不同作用域）</li>
          <li><strong>浏览器限制</strong>：某些浏览器对Cookie大小和数量有限制</li>
        </ul>

        <h3>最佳实践：3</h3>
          <ol>
            <li>始终明确设置Domain和Path属性</li>
            <li>使用一致的Secure和HttpOnly设置</li>
            <li>避免在多个作用域创建同名Cookie</li>
            <li>在更新Cookie时保留原始的所有配置</li>
            <li>考虑使用Cookie前缀（__Secure-, __Host-）增强安全性</li>
          </ol>
      </div>
    </div>
  </div>
</div>

<script>
  function updateCookieDisplay() {
    const cookies = document.cookie.split(';');
    let html = '';

    if (cookies.length === 0 || (cookies.length === 1 && cookies[0] === '')) {
      html = '> 当前没有Cookie';
    } else {
      cookies.forEach(cookie => {
        if (cookie.trim() !== '') {
          const [name, value] = cookie.split('=');
          html += `> ${name.trim()}: ${value.trim()}\n`;
        }
      });
    }

    document.getElementById('currentCookies').textContent = html;
  }

  function setCookie() {
    const name = document.getElementById('cookieName').value || 'user_session';
    const value = document.getElementById('cookieValue').value || 'initial_value';
    const maxAge = parseInt(document.getElementById('maxAge').value) || 86400;

    // 设置初始Cookie
    document.cookie = `${name}=${value}; max-age=${maxAge}; path=/; samesite=lax`;

    showResult(`✅ 已设置Cookie: ${name}=${value} (有效期: ${maxAge}秒)`);
    updateCookieDisplay();
  }

  function updateCookie() {
    const name = document.getElementById('cookieName').value || 'user_session';
    const value = `updated_${Math.floor(Math.random() * 1000)}`;
    const maxAge = parseInt(document.getElementById('maxAge').value) || 86400;

    // 覆盖同名Cookie
    document.cookie = `${name}=${value}; max-age=${maxAge}; path=/; samesite=lax`;

    showResult(`🔄 已更新Cookie: ${name}=${value} (有效期: ${maxAge}秒)`);
    updateCookieDisplay();
  }

  function deleteCookie() {
    const name = document.getElementById('cookieName').value || 'user_session';

    // 通过设置过期时间删除Cookie
    document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;

    showResult(`🗑️ 已删除Cookie: ${name}`);
    updateCookieDisplay();
  }

  function clearAll() {
    // 清除所有演示相关的Cookie
    document.cookie.split(';').forEach(cookie => {
      const [name] = cookie.split('=');
      document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
    });

    showResult('🧹 已清除所有演示Cookie');
    updateCookieDisplay();
  }

  function showResult(message) {
    const resultContainer = document.getElementById('cookieResults');
    const resultElement = document.createElement('div');
    resultElement.className = 'cookie-item';
    resultElement.innerHTML = `<div class="cookie-name">操作结果</div><div class="cookie-value">${message}</div>`;

    resultContainer.prepend(resultElement);

    // 保留最新的3条结果
    while (resultContainer.children.length > 3) {
      resultContainer.removeChild(resultContainer.lastChild);
    }
  }

  // 初始化显示当前Cookie状态
  window.addEventListener('DOMContentLoaded', updateCookieDisplay);
</script>
</body>
</html>
